﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security;
using System.IO;
using System.ServiceModel.Web;
using System.Net;
using System.Collections.Specialized;

namespace COFE.Web.Services
{
    /// <summary>
    /// Utils for Http Web Services.
    /// </summary>
    public static class ServicesUtils
    {
        public static string UrlPathToCOFEPath(string urlPath)
        {
            return urlPath.Replace('/', '\\');
        }

        /// <summary>
        /// Set Content-Disposition to attachment with filename, and set output content type to file stream.
        /// </summary>
        /// <param name="fileName"></param>
        public static void SetContentHeaderAsFile(string fileName)
        {
            var response = WebOperationContext.Current.OutgoingResponse;

            response.Headers.Add("Content-Disposition", string.Format("attachment; filename={0}", fileName));
            response.Headers.Add(HttpResponseHeader.ContentType, "application/octet-stream");
            response.Headers.Add("X-Filename", fileName);
        }

        /// <summary>
        /// Set output response to the specified desturi.
        /// </summary>
        /// <param name="desturi"></param>
        public static void SetRedirect(string uri, bool forwardAttributes = false, params string[] skippedAttributes)
        {
            if (forwardAttributes)
            {
                NameValueCollection nameValueCol = HttpUtility.ParseQueryString(HttpContext.Current.Request.Url.Query);
                
                foreach (var key in nameValueCol.AllKeys)
                    if (!(skippedAttributes.Contains(key.ToLower())))
                    {
                        if (uri.IndexOf('?') == -1)
                            uri += "?";
                        else uri += "&";

                        uri += String.Format("{0}={1}", key, nameValueCol[key]);
                    }
            }

            WebOperationContext.Current.OutgoingResponse.Location = uri;
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Redirect;
        }

        private static void prepare(bool signIn)
        {
            ServiceConstants.Host = WSUtils.GetHost();
            //if (!WSUtils.LoginCurrentUser()) throw new SecurityException();
            if (signIn)
            {
                if (!WSUtils.LoginUsingCookie()) throw new SecurityException();
                //if (!WSUtils.Login("Test", "Test")) throw new SecurityException();
                if (ServiceConstants.Profile == null) throw new SecurityException();
            }
        }

        /// <summary>
        /// Execute the specified action and return an output, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="I"></typeparam>
        /// <typeparam name="I2"></typeparam>
        /// <typeparam name="O"></typeparam>
        /// <param name="action"></param>
        /// <param name="input"></param>
        /// <param name="input2"></param>
        /// <returns></returns>
        public static O ExecuteAndReturn<I, I2, O>(Func<I, I2, O> action, I input, I2 input2, bool signIn = true)
        {
            try
            {
                prepare(signIn);
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;
                return action(input, input2);
            }
            catch (SecurityException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }

            return default(O);
        }


        /// <summary>
        /// Execute the specified action and return an output, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="I"></typeparam>
        /// <typeparam name="O"></typeparam>
        /// <param name="action"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public static O ExecuteAndReturn<I, O>(Func<I, O> action, I input, bool signIn = true)
        {
            try
            {
                prepare(signIn);
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;
                
                return action(input);
            }
            catch (SecurityException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }

            return default(O);
        }

        public static O2 ExecuteAndReturn<I1, I2, O1, O2>(
            Func<I1, I2, O1> fastFunc,
            Func<O1, I1, I2, O2> slowFunc,
            Func<O1, I1, I2, DateTime> getLastUpdateFunc,
            I1 input1, I2 input2, bool signIn = true)
        {
            try
            {
                prepare(signIn);

                O1 fastVal = fastFunc(input1, input2);
                DateTime lastUpdate = getLastUpdateFunc(fastVal, input1, input2);
                lastUpdate = new DateTime(lastUpdate.Ticks - (lastUpdate.Ticks % TimeSpan.TicksPerSecond), lastUpdate.Kind);

                bool cont = true;
                if (WebOperationContext.Current.IncomingRequest.IfModifiedSince.HasValue)
                    cont = lastUpdate != WebOperationContext.Current.IncomingRequest.IfModifiedSince.Value;

                if (!cont)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotModified;
                    return default(O2);
                }
                else
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;
                    WebOperationContext.Current.OutgoingResponse.LastModified = lastUpdate;
                    return slowFunc(fastVal, input1, input2);
                }
            }
            catch (SecurityException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }

            return default(O2);
        }



        public static O2 ExecuteAndReturn<I, O1, O2> (
            Func<I, O1> fastFunc,
            Func<O1, I, O2> slowFunc,
            Func<O1, I, DateTime> getLastUpdateFunc,
            I input, bool signIn = true)
        {
            return ExecuteAndReturn<I, I, O1, O2>(
                (i1, i2) => fastFunc(i1),
                (o1, i1, i2) => slowFunc(o1, i1),
                (o1, i1, i2) => getLastUpdateFunc(o1, i1), input, input, signIn);
            
        }

        /// <summary>
        /// Execute the specified action and return an output, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="O"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static O ExecuteAndReturn<O>(Func<O> action, bool signIn = true)
        {
            try
            {
                prepare(signIn);
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;

                return action();
            }
            catch (SecurityException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }

            return default(O);
        }

        /// <summary>
        /// Execute the specified action, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="I"></typeparam>
        /// <typeparam name="I2"></typeparam>
        /// <param name="action"></param>
        /// <param name="input"></param>
        /// <param name="input2"></param>
        public static void Execute<I, I2>(Action<I, I2> action, I input, I2 input2, bool signIn = true)
        {
            try
            {
                prepare(signIn);
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;

                action(input, input2);
            }
            catch (SecurityException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }
        }

        /// <summary>
        /// Execute the specified action, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="I"></typeparam>
        /// <param name="action"></param>
        /// <param name="input"></param>
        public static void Execute<I>(Action<I> action, I input, bool signIn = true)
        {
            try
            {
                prepare(signIn);

                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;

                action(input);
            }
            catch (SecurityException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }
        }


        /// <summary>
        /// Execute the specified action, catch all exception and return a related appropriate status code.
        /// </summary>
        /// <typeparam name="I"></typeparam>
        /// <param name="action"></param>
        /// <param name="input"></param>
        public static void Execute(Action action, bool signIn = true)
        {
            try
            {
                prepare(signIn);

                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;

                action();
            }
            catch (SecurityException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; }
            catch (ArgumentException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed; }
            catch (FileNotFoundException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (KeyNotFoundException ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; }
            catch (Exception ex) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; }
        }        
    }
}